package physics;

import java.awt.event.ComponentEvent;
import physics.body.Shape;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;
import java.util.Stack;
import javax.swing.JFrame;
import physics.body.Rect;

/**
 *
 * @author peter
 */
public class Engine extends Canvas {
	public static final int YIELD_COUNT = 1000;
	public static final int INIT_SCREEN_WIDTH = 1000;
	public static final int INIT_SCREEN_HEIGHT = 600;
	private ArrayList<Shape> shapes;
	private boolean running = true;
	private BufferStrategy strategy;
	private Stack<KeyEvent> keyPresses;
	private int screenWidth;
	private int screenHeight;
	private PVector gravity;

	public int getScreenHeight()
	{
		return screenHeight;
	}

	public void setScreenHeight(int screenHeight)
	{
		this.screenHeight = screenHeight;
	}

	public int getScreenWidth()
	{
		return screenWidth;
	}

	public void setScreenWidth(int screenWidth)
	{
		this.screenWidth = screenWidth;
	}

	public Engine(final JFrame jframe)
	{
		gravity = new PVector(0, 0);
		screenWidth = jframe.getWidth();
		screenHeight = jframe.getHeight();
		jframe.add(this);
		keyPresses = new Stack<KeyEvent>();
		shapes = new ArrayList<Shape>();
		requestFocus();

		// set up the frame
		jframe.setSize(INIT_SCREEN_WIDTH, INIT_SCREEN_HEIGHT);

		jframe.addComponentListener(new ComponentListener() {
			public void componentResized(ComponentEvent e)
			{
				screenWidth = jframe.getWidth();
				screenHeight = jframe.getHeight();
			}

			public void componentMoved(ComponentEvent e)
			{
			}

			public void componentShown(ComponentEvent e)
			{
			}

			public void componentHidden(ComponentEvent e)
			{
			}
		});

		addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e)
			{
				keyPresses.push(e);
			}
		});
	}

	private void init()
	{
		removeAllShapes();
		createBufferStrategy(2);

//		gravity.setY(-5);

		strategy = this.getBufferStrategy();
		Rect r1 = new Rect(100, 500, 50, 50);
//		r1.setMass(s);
		addShape(r1);
//		Rect r3 = new Rect(685, 300, 60, 50);
//		addShape(r3);
//		r3.adjustAngularVelo(0.15);
//		r1.setMass(2.0);
		Rect wBottom = new Rect(screenWidth / 2, 40, screenWidth - 100, 30);
		Rect wTop = new Rect(screenWidth / 2, screenHeight - 15, screenWidth - 100, 30);
		Rect wRight = new Rect(screenWidth - 30, (screenHeight - 10) / 2, 30, screenHeight - 100);
		Rect wLeft = new Rect(15, (screenHeight - 10) / 2, 30, screenHeight - 100);

		wBottom.setMass(1000000);
		wTop.setMass(1000000);
		wRight.setMass(1000000);
		wLeft.setMass(1000000);

//		addShape(wBottom);
//		addShape(wTop);
//		addShape(wRight);
//		addShape(wLeft);

//		r3.setMoveable(false);
//		Rect r3 = new Rect(200, 100, 50, 50);
//		Rect r4 = new Rect(175, 300, 50, 50);
//		r1.setVelocity(new PVector(2, 2));
//		r2.setVelocity(new PVector(2.5f, 0));
//		r1.setMass(2.0);

//		int number = 10;
//		int gap = 8;
//		int size = 50;
//		for (int i = 0; i < number; i++)
//			addShape(new Rect(i * (size + gap) + 300, 200, size, size));
//		for (int i = 0; i < number; i++)
//			addShape(new Rect(i*(size + gap) + 300, 370, size, size));
//		for (int i = 0; i < number; i++)
//			addShape(new Rect(i*(size + gap) + 300, 440, size, size));


//
//		for (int i = 1; i <= rows; i++){
//			for (int j = 0; j < i; j++){
//				addShape(new Rect(
//						400 + j*(size + gap),
//						i * -55 + 500,
//						size,
//						size));
////				addShape(new Rect(
////						400 - (j+1)*(size + gap),
////						i * -55 + 500,
////						size,
////						size));
//			}
//		}
//		Shape big = new Rect(400, 400, 400, 50);
//		big.setMass(4);
//		addShape(big);
//		addShape(new Rect(100, 100, 50, 50));
//		addShape(new Rect(160, 100, 50, 50));
//		addShape(new Rect(160, 160, 100, 50));
//
		int rows = 1;
		int columns = 8;
		int hoffset = 300;
		int voffset = 100;
		int vsize = 50;
		int hsize = 50;
		int gap = 20;
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				addShape(new Rect(hoffset + j * (hsize + gap), voffset + i * (vsize + gap), hsize, vsize));
		Shape hit1 = new Rect(30, vsize + gap + 15, 50, 50);
		hit1.adjustVelocity(new PVector(6, 1));
		hit1.adjustAngularVelo(-0.5);
		addShape(hit1);
		addShape(new Rect(120, 300, 100, 20));

		Shape hit2 = new Rect(400, 475, 50, 50);
		hit2.adjustVelocity(new PVector(4, 1));
		hit2.adjustAngularVelo(-0.1);
		addShape(hit2);
		addShape(new Rect(600, 500, 50, 50));


//		Shape a = new Rect(300, 300, 50, 50);
////		a.setVelocity(new PVector(3, 0));
//		addShape(a);
//		Shape b = new Rect(400, 325, 50, 50);
//		b.setVelocity(new PVector(30,0));
//		addShape(b);
//
//		Shape c = new Rect(500, 350, 50, 50);
//		c.setMass(5);
//		addShape(c);

//		addShape(new Rect(300, 300, size, size));
//		addShape(new Rect(351, 326, size, size));
//		addShape(new Rect(351, 274, size, size));
//		addShape(new Rect(402, 326, size, size));
//		addShape(new Rect(402, 274, size, size));



//		addShape(r3);
//		addShape(r4);
	}

	public void start()
	{
		init();

		float target = 1000 / 60.0f;
		float frameAverage = target;
		long lastFrame = System.currentTimeMillis();

		long renderTime = 0;
		long logicTime = 0;

		while (running) {

			long initTime = System.currentTimeMillis();
			frameAverage = (frameAverage * 10 + (initTime - lastFrame)) / 11;
			lastFrame = initTime;

			for (int i = 0; i < YIELD_COUNT; i++)
				Thread.yield();

			// render
			long beforeRender = System.currentTimeMillis();
			Graphics2D g = (Graphics2D) strategy.getDrawGraphics();

			g.setColor(Color.WHITE);
			g.fillRect(0, 0, screenWidth, screenHeight);

			g.setColor(Color.BLACK);
			for (Shape s : shapes)
				s.draw(g, screenHeight);

			g.drawString("FPS: " + (int) (1000 / frameAverage), 10, 70);

			g.dispose();
			strategy.show();

			renderTime = System.currentTimeMillis() - beforeRender;

			// update data
			long beforeLogic = System.currentTimeMillis();

			while (keyPresses.size() > 0) {
				KeyEvent ke = keyPresses.pop();
				switch (ke.getKeyCode()) {
					case KeyEvent.VK_R:
						init();
						break;
					case KeyEvent.VK_RIGHT:
						shapes.get(0).adjustForce(new PVector(100f, 0));
						break;
					case KeyEvent.VK_LEFT:
						shapes.get(0).adjustForce(new PVector(-100f, 0));
						break;
					case KeyEvent.VK_UP:
						shapes.get(0).adjustVelocity(new PVector(0, 1f));
						break;
					case KeyEvent.VK_DOWN:
						shapes.get(0).adjustVelocity(new PVector(0, -1f));
						break;
					case KeyEvent.VK_PERIOD:
						shapes.get(0).adjustAngularVelo(-0.05);
						break;
					case KeyEvent.VK_COMMA:
						shapes.get(0).adjustAngularVelo(0.05);
						break;
					case KeyEvent.VK_W:
						gravity.setY(gravity.getY() + 1);
						break;
					case KeyEvent.VK_S:
						gravity.setY(gravity.getY() - 1);
						break;
				}
			}

			step();
			logicTime = System.currentTimeMillis() - beforeLogic;
		}
	}

	private void step()
	{
		step(1 / 60.0f);
	}

	private void step(float delta)
	{
		for (int i = 0; i < shapes.size(); i++) {
			Shape s = shapes.get(i);
			s.setDirty(false);

			// detect collisions
			for (int j = i + 1; j < shapes.size(); j++)
				if (Collision.isCollision(s, shapes.get(j), delta)) {
					s.setDirty(true);
					shapes.get(j).setDirty(true);
				}

			// adjust position
			PVector temp = new PVector(s.getForce());
			if (s.isGravity())
				temp.add(gravity);
			temp.scale(delta);
			s.adjustVelocity(temp);

			s.adjustPosition(s.getVelocity(), delta);

			// adjust angles
			s.adjustAngularVelo(delta * s.getTorque());
			s.adjustRotation(delta * s.getAngularVelo());

			s.setForce(new PVector(0, 0));
			s.setTorque(0);
		}

		for (int i = 0; i < shapes.size(); i++) {
			Shape s = shapes.get(i);

			if (s.isDirty()) {
				System.out.println("corrected collison into another rect!");
				s.setDirty(false);

				// detect collisions
				for (int j = i + 1; j < shapes.size(); j++) {
					if (Collision.isCollision(s, shapes.get(j), delta)) {
						s.setDirty(true);
						shapes.get(j).setDirty(true);
					}
				}

				// adjust position
				PVector temp = new PVector(s.getForce());
				if (s.isGravity())
					temp.add(gravity);
				temp.scale(delta);
				s.adjustVelocity(temp);
				s.adjustPosition(s.getVelocity(), delta);

				// adjust angles
				s.adjustAngularVelo(delta * s.getTorque());
				s.adjustRotation(delta * s.getAngularVelo());

				s.setForce(new PVector(0, 0));
				s.setTorque(0);
			}
		}

		//freeeeeeeze
		for (int i = 0; i < shapes.size(); i++) {
			Shape s = shapes.get(i);
			if (s.isDirty()) {
				s.setDirty(false);
				// detect collisions
				for (int j = i + 1; j < shapes.size(); j++) {
					Shape other = shapes.get(j);
					if (Collision.isCollision(s, other, delta)) {
						s.setDirty(true);
						shapes.get(j).setDirty(true);
						System.out.println("still intersecting, freezing.");
//						try {
//							Thread.sleep(500);
//						} catch (InterruptedException ex) {
//							Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, null, ex);
//						}
						s.setVelocity(new PVector(0, 0));
						s.setAngularVelo(0);
					//		other.setVelocity(new PVector(0, 0));
					//		other.setAngularVelo(0);
					}
				}
				s.setForce(new PVector(0, 0));
				s.setTorque(0);
			}
		}


	}

	public void removeAllShapes()
	{
		shapes.clear();
	}

	public void setRunning(boolean running)
	{
		this.running = running;
	}

	public void addShape(Shape s)
	{
		shapes.add(s);
	}
}
